// 网络请求组件
//
// 此组件依赖model.debugs进行日志记录，请注意协调
package https

import (
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"gitee.com/xiaoyutab/xgotool/xnum"
)

// 网址服务对象
type CURL struct {
	Uri       string            `json:"uri"`        // 请求网址
	Param     map[string]string `json:"param"`      // 请求参数
	HttpCode  int               `json:"http_code"`  // HTTP返回的code值
	ParamJson map[string]any    `json:"param_json"` // JSON推送参数
	Body      string            `json:"body"`       // 返回值
	Error     error             `json:"error"`      // 错误信息
	Header    map[string]string `json:"header"`     // 请求Header头
	StartTime time.Time         `json:"start_time"` // 请求开始时间
	EndTime   time.Time         `json:"end_time"`   // 请求结束时间
	OverTime  time.Time         `json:"over_time"`  // Json提取处理结束时间
	ClientIp  string            `json:"client_ip"`  // 请求的客户端IP【兼容日志处理模块的预留字段】
}

// 配置请求信息
type Config struct {
	LogsFunc       func(c *CURL) // 日志记录函数【其param和param_json可能为截取后的结果，但返回值body为完整返回值】
	Https          bool          // 是否跳过HTTPS证书效验
	TimeOut        time.Duration // 请求超时时间
	client         *http.Client  // 内部私有变量，用于导入自定义的Client请求头
	MaxParamLength int           // 最大参数长度【超过该长度的字符串将会进行截取`MaxParamLength - 3 + "..."`】(直接传入PostJson(_:xxx)的方法无法进行截取)
	NoBodyRouter   []string      // 不记录Body返回值的网址请求，不支持正则表达式，支持开头匹配
	NoHeaderKeys   []string      // 不需要记录的Header信息[不区分大小写]
}

// 默认配置项
var _default Config = Config{
	MaxParamLength: 100,
	NoHeaderKeys: []string{"accept", "Accept",
		"content-type", "Content-Type",
		"accept-language", "Accept-Language",
		"user-agent", "User-Agent",
		"authorization", "Authorization",
	},
	NoBodyRouter: []string{},
}

// 注入配置项
func Regedit(c *Config) {
	if c == nil {
		return
	}
	if c.LogsFunc != nil {
		_default.LogsFunc = c.LogsFunc
	}
	_default.Https = c.Https
	if c.TimeOut > 0 {
		_default.TimeOut = c.TimeOut
	}
	if c.MaxParamLength > 0 {
		_default.MaxParamLength = c.MaxParamLength
	}
	// header追加
	for i := 0; i < len(c.NoHeaderKeys); i++ {
		if xnum.InArray(strings.ToLower(c.NoHeaderKeys[i]), _default.NoHeaderKeys) {
			continue
		}
		_default.NoHeaderKeys = append(_default.NoHeaderKeys, strings.ToLower(c.NoHeaderKeys[i]))
	}
	// nobody追加
	for i := 0; i < len(c.NoBodyRouter); i++ {
		if xnum.InArray(c.NoBodyRouter[i], _default.NoBodyRouter) {
			continue
		}
		_default.NoBodyRouter = append(_default.NoBodyRouter, c.NoBodyRouter[i])
	}
}

// 日志记录函数，用于调用default中的日志记录函数来存储日志信息
//
//	curl	请求的结构体信息
func questLogFunc(curl *CURL) {
	// 参数过滤，将超长的Param和ParamJson给截取部分
	for i, v := range curl.Param {
		if len(v) > _default.MaxParamLength {
			curl.Param[i] = v[:_default.MaxParamLength-3] + "..."
		}
	}
	for i, v := range curl.ParamJson {
		if i == "_" {
			// 如果是直接传入的JSON，此处将不再进行处理
			break
		}
		if vs, ok := v.(string); ok && len(vs) > _default.MaxParamLength {
			curl.ParamJson[i] = vs[:_default.MaxParamLength-3] + "..."
		}
	}
	// Header请求头过滤
	for i := 0; i < len(_default.NoHeaderKeys); i++ {
		delete(curl.Header, _default.NoHeaderKeys[i])
	}
	// Body请求留空
	for i := 0; i < len(_default.NoBodyRouter); i++ {
		if len(curl.Uri) >= len(_default.NoBodyRouter[i]) && _default.NoBodyRouter[i] == curl.Uri[:len(_default.NoBodyRouter[i])] {
			// Body清空的操作会影响后续的Query查询
			// 所以此处判断如果是body清空的操作的话，就额外创建一个变量进行复制curl，再行清空、记录日志
			cu := *curl
			cu.Body = ""
			if _default.LogsFunc != nil {
				go _default.LogsFunc(&cu)
			}
			return
		}
	}
	if _default.LogsFunc != nil {
		go _default.LogsFunc(curl)
	}
}

// 从quest结构体中获取到数据并回写到CURL结构体中
//
//	req		请求的待处理操作
//	file	下载的文件名信息（仅第一个有效，后续的file为无效参数）
func (curl *CURL) queryBody(req *http.Request, file ...string) *CURL {
	// 设置Header请求头
	for i, v := range curl.Header {
		req.Header.Set(i, v)
	}
	// 执行HTTP请求
	resp, err := client().Do(req)
	if err != nil {
		curl.Error = err
		return curl
	}
	defer resp.Body.Close()
	// 获取相应结果
	bodys, err := io.ReadAll(resp.Body)
	if err != nil {
		curl.Error = err
		return curl
	}
	if len(file) > 0 {
		curl.Body = "下载文件操作，内容省略"
		// 写入内容到文件中
		f, err := os.OpenFile(file[0], os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
		if err != nil {
			// 如果无法打开的话
			curl.Error = err
			return curl
		}
		// 写入文件
		defer f.Close()
		f.Write(bodys)
	} else {
		curl.Body = string(bodys)
	}
	curl.HttpCode = resp.StatusCode
	curl.EndTime = time.Now()
	return curl
}

// 获取追加Param参数的结构体信息
//
//	uri		待拼接URL，传入空表示为POST请求，仅做参数拼接
//	param	带拼接参数
func getParam(uri string, param map[string]string) string {
	// POST参数拼接
	dat := url.Values{}
	for i, v := range param {
		dat.Set(i, v)
	}
	if uri == "" {
		return dat.Encode()
	}
	if strings.ContainsAny(uri, "?") {
		uri += "&" + dat.Encode()
	} else {
		uri += "?" + dat.Encode()
	}
	return uri
}
